﻿/*
 * KRPCDataInfo
 * 
 * Represents all KRPCDataInfo
 * 
 * KRPCData
 * 
 * Represents all data in KRPC (parameters, return value and soon)
 * 
 * Author: snowdreamist
 * Date: 2011/12/11
 * 
 */

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using BitTorrentSharp.KRPC.Messages;
using BitTorrentSharp.KRPC.Contract;

namespace BitTorrentSharp.KRPC
{
    public class KRPCDataInfo
    {
        #region static
        /// <summary>
        /// Default data info
        /// </summary>
        public readonly static KRPCDataInfo DefaultDataInfo = new KRPCDefaultDataInfo();
        /// <summary>
        /// Lock object
        /// </summary>
        private readonly static object _syncRoot = new object();
        /// <summary>
        /// DataInfo dict
        /// </summary>
        private readonly static Dictionary<Type, KRPCDataInfo> _dataInfoDict = new Dictionary<Type, KRPCDataInfo>();
        /// <summary>
        /// Get data info
        /// </summary>
        /// <param name="dataType">the data type</param>
        /// <returns>DataInfo</returns>
        public static KRPCDataInfo GetDataInfo(Type dataType)
        {
            if (dataType == null)
                throw new ArgumentNullException("dataType");

            // TODO: handle ref type

            if (_dataInfoDict.ContainsKey(dataType))
            {
                return _dataInfoDict[dataType];
            }
            else
            {
                lock (_syncRoot)
                {
                    if (_dataInfoDict.ContainsKey(dataType))
                    {
                        return _dataInfoDict[dataType];
                    }
                    else
                    {
                        var attrs = dataType.GetCustomAttributes(typeof(KRPCDataContractAttribute), false);
                        KRPCDataInfo dataInfo = null;

                        if (attrs == null || attrs.Length == 0)
                        {
                            // check if it fits default one
                            if (KRPCDefaultDataInfo.IsTypeFit(dataType))
                            {
                                // fit!
                                dataInfo = new KRPCDataInfo(dataType, attrs[0] as KRPCDataContractAttribute);
                            }
                            else
                            {
                                throw new KRPCContractLoadException(string.Format("No KRPCDataContract found on type {0}", dataType.Name));
                            }
                        }
                        else if (attrs.Length > 1)
                        {
                            throw new KRPCContractLoadException("Only one KRPCDataContract is allowed");
                        }
                        else
                        {
                            dataInfo = new KRPCDataInfo(dataType, attrs[0] as KRPCDataContractAttribute);
                        }

                        _dataInfoDict[dataType] = dataInfo;
                        // return
                        return dataInfo;
                    }
                }
            }
        }

        #endregion
        /// <summary>
        /// ContractWrapper
        /// </summary>
        public DataContractWrapper Wrapper { get; private set; }
        /// <summary>
        /// Serializer
        /// </summary>
        public Lazy<IMessageSerializer> Serializer { get; private set; }
        /// <summary>
        /// Default value of this data
        /// </summary>
        public object DefaultValue { get; private set; }
        /// <summary>
        /// Constructor
        /// </summary>
        protected KRPCDataInfo() { }
        /// <summary>
        /// Constructor
        /// </summary>
        protected KRPCDataInfo(Type dataType, KRPCDataContractAttribute attr)
        {
            KRPCHost host = KRPCHost.Current;
            // wrapper
            this.Wrapper = new DataContractWrapper(attr);
            // serializer
            if (this.Wrapper.SerializerFactoryType == null)
                this.Serializer = new Lazy<IMessageSerializer>(() => host.MessageSerializerFactory.Create(this.Wrapper.SerializerType));
            else
                this.Serializer = new Lazy<IMessageSerializer>(
                    () => host.MessageSerializerFactoryFactory.Create(this.Wrapper.SerializerFactoryType).Create(this.Wrapper.SerializerType)
                    );
        }
    }

    public class KRPCDefaultDataInfo : KRPCDataInfo
    {
        public static bool IsTypeFit(Type dataType)
        {
            throw new NotImplementedException();
        }
    }

    public class KRPCData : KRPCNameValue
    {
        /// <summary>
        /// Constructor
        /// </summary>
        /// <param name="info"></param>
        public KRPCData(KRPCDataInfo info)
        {
            if (info == null)
                throw new ArgumentNullException("info");
            this.Info = info;
            this.Value = this.Info.DefaultValue;
        }
        /// <summary>
        /// Constructor
        /// </summary>
        /// <param name="info">DataInfo</param>
        public KRPCData(KRPCDataInfo info, string name)
            : base(name)
        {
            if (info == null)
                throw new ArgumentNullException("info");
            this.Info = info;
            this.Value = this.Info.DefaultValue;
        }
        /// <summary>
        /// Constructor
        /// </summary>
        /// <param name="info">DataInfo</param>
        /// <param name="value">Value</param>
        public KRPCData(KRPCDataInfo info, object value)
        {
            if (info == null)
                throw new ArgumentNullException("info");
            this.Info = info;
            this.Value = value;
        }

        public KRPCData(KRPCDataInfo info, string name, object value)
            : base(name, value)
        {
            if (info == null)
                throw new ArgumentNullException("info");
            this.Info = info;
        }
        /// <summary>
        /// Data info
        /// </summary>
        public KRPCDataInfo Info { get; private set; }
    }
}
